// Daniel Smith

#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <vector>
#include <iostream>
#include <math.h>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace std;

int main()
{
    const unsigned int ppo = 10 + 1;
    const double pi = 3.14159265358979324;
    const unsigned int startObjects = 100;
    double gravity = 1, upf = 10;
    vector<double> temp(10);
    vector<unsigned int> mode(3 + 1); // 0 = draw mode, 1 = mode count, 2 = pause, 3 = trace
    mode.at(1) = 3;
    vector<double> mouse(6 + 1, 0); // 0 1 = mouse x y, 2 3 = clicked x y, 4 = draw, 5 radius, 6 counter
    vector<double> objects(startObjects * ppo); // 0 = x, 1 = y, 2 = x velocity, 3 = y velocity, 4 = mass, 5 = alive , 6 7 8 = r g b, 9 = radious, 10 stationary
    vector<double> distance(startObjects * ppo * 3);
    vector<double> camera(2 + 1); // 0 = x, 1 = y, 2 = zoom factor
    camera.at(2) = 1;

    sf::RenderWindow Window(sf::VideoMode::GetDesktopMode(), "Gravity", sf::Style::Fullscreen);
    Window.UseVerticalSync(1);
    W

    sf::Event Event;
    sf::Input Input;

    for (unsigned int loop1 = 0; loop1 < startObjects; loop1++) // initial conditions
    {
        objects.at(loop1 * ppo) = sf::Randomizer::Random(0, Window.GetWidth());
        objects.at(loop1 * ppo + 1) = sf::Randomizer::Random(0, Window.GetHeight());
        objects.at(loop1 * ppo + 2) = 0;
        objects.at(loop1 * ppo + 3) = 0;
        objects.at(loop1 * ppo + 4) = sf::Randomizer::Random(50, 100);
        objects.at(loop1 * ppo + 5) = 1;
        objects.at(loop1 * ppo + 6) = sf::Randomizer::Random(0, 255);
        objects.at(loop1 * ppo + 7) = sf::Randomizer::Random(0, 255);
        objects.at(loop1 * ppo + 8) = sf::Randomizer::Random(0, 255);
        objects.at(loop1 * ppo + 9) = sqrt(objects.at(loop1 * ppo + 4) / pi);
        objects.at(loop1 * ppo + 10) = 0;
    }

    while (Window.IsOpened())
    {
        while (Window.GetEvent(Event))
        {
            switch (Event.Type)
            {
                default:
                {
                    break;
                }
                case sf::Event::Closed: // exit
                {
                    Window.Close();
                    break;
                }
                case sf::Event::KeyPressed:
                {
                    switch (Event.Key.Code) // process key events
                    {
                        default:
                        {
                            break;
                        }
                        case sf::Key::Escape:
                        {
                            Window.Close();
                            break;
                        }
                        case sf::Key::C:
                        {
                            objects.resize(0);
                            distance.resize(0);
                            Window.Clear();
                            break;
                        }
                        case sf::Key::Space:
                        {
                            mode.at(2) = !mode.at(2);
                            break;
                        }
                        case sf::Key::V:
                        {
                            mode.at(0) = (mode.at(0) + 1) % mode.at(1);
                            break;
                        }
                        case sf::Key::T:
                        {
                            mode.at(3) = !mode.at(3);
                            break;
                        }
                        case sf::Key::H:
                        {
                            camera.at(0) = 0;
                            camera.at(1) = 0;
                            camera.at(2) = 1;
                            break;
                        }
                        case sf::Key::S:
                        {
                            ofstream output;
                            output.open ("save.grv");
                            temp.at(0) = 0;
                            for(unsigned int loop1 = 0; loop1 < objects.size() / ppo; loop1++)
                            {
                                if(objects.at(loop1 * ppo + 5))
                                {
                                    temp.at(0)++;
                                }
                            }
                            output << temp.at(0) << endl << ppo << endl << upf << camera.at(0) << endl << camera.at(1) << endl << camera.at(2) << endl << mode.at(0) << endl << mode.at(1) << endl << mode.at(2) << endl << mode.at(3) << endl;
                            for(unsigned int loop1 = 0; loop1 < (objects.size() / ppo); loop1++)
                            {
                                if(objects.at(loop1 * ppo + 5))
                                {
                                    for(unsigned int loop2 = 0; loop2 < ppo; loop2++)
                                    {
                                        output << objects.at(loop1 * ppo + loop2) << endl;
                                    }
                                }
                            }
                            output.close();

                            break;
                        }
                        case sf::Key::L:
                        {
                            break;
                        }
                        case sf::Key::Up:
                        {
                            camera.at(1) -= 100;
                            break;
                        }
                        case sf::Key::Down:
                        {
                            camera.at(1) +=  100;
                            break;
                        }
                        case sf::Key::Left:
                        {
                            camera.at(0) -= 100;
                            break;
                        }
                        case sf::Key::Right:
                        {
                            camera.at(0) += 100;
                            break;
                        }
                        case sf::Key::Add:
                        {
                            upf += 100;
                            break;
                        }
                        case sf::Key::Subtract:
                        {
                            upf -= 100;
                            if(upf < 0)
                            {
                                upf = 10;
                            }
                            break;
                        }
                    }
                    break;
                }
            }

            if(Event.Type == Event.MouseMoved) // detect mouse movement
            {
                mouse.at(0) = Event.MouseMove.X;
                mouse.at(1) = Event.MouseMove.Y;
                break;
            }

            if(Event.Type == Event.MouseWheelMoved)
            {
                camera.at(2) *= pow(2, Event.MouseWheel.Delta);
                break;
            }

            if (mouse.at(4)) // check if we have any past clicks
            {
                if (Event.Type == Event.MouseButtonReleased) // detect button pressed and handle case
                {
                    if (Event.MouseButton.Button == sf::Mouse::Left)
                    {
                        objects.resize(objects.size() + ppo);
                        distance.resize(objects.size() * 3);

                        objects.at(objects.size() - ppo) = mouse.at(2);
                        objects.at(objects.size() - ppo + 1) = mouse.at(3);
                        objects.at(objects.size() - ppo + 2) = 0.1 * (mouse.at(0) - mouse.at(2) + camera.at(0));
                        objects.at(objects.size() - ppo + 3) = 0.1 * (mouse.at(1) - mouse.at(3) + camera.at(1));
                        objects.at(objects.size() - ppo + 4) = pi * pow(mouse.at(6) / (10), 2);
                        objects.at(objects.size() - ppo + 5) = 1;
                        objects.at(objects.size() - ppo + 6) = sf::Randomizer::Random(0, 255);
                        objects.at(objects.size() - ppo + 7) = sf::Randomizer::Random(0, 255);
                        objects.at(objects.size() - ppo + 8) = sf::Randomizer::Random(0, 255);
                        objects.at(objects.size() - ppo + 9) = sqrt(objects.at(objects.size() - ppo + 4) / pi);
                        objects.at(objects.size() - ppo + 10) = 0;

                        mouse.at(4) = 0;
                        mouse.at(6) = 0;
                    }
                    else // right
                    {
                        objects.resize(objects.size() + ppo);
                        distance.resize(objects.size() * 3);

                        objects.at(objects.size() - ppo) = mouse.at(2);
                        objects.at(objects.size() - ppo + 1) = mouse.at(3);
                        objects.at(objects.size() - ppo + 2) = 0;
                        objects.at(objects.size() - ppo + 3) = 0;
                        objects.at(objects.size() - ppo + 4) = pi * pow(mouse.at(6) / (10), 2);
                        objects.at(objects.size() - ppo + 5) = 1;
                        objects.at(objects.size() - ppo + 6) = sf::Randomizer::Random(0, 255);
                        objects.at(objects.size() - ppo + 7) = sf::Randomizer::Random(0, 255);
                        objects.at(objects.size() - ppo + 8) = sf::Randomizer::Random(0, 255);
                        objects.at(objects.size() - ppo + 9) = sqrt(objects.at(objects.size() - ppo + 4) / pi);
                        objects.at(objects.size() - ppo + 10) = 1;

                        mouse.at(4) = 0;
                        mouse.at(6) = 0;
                    }
                }
            }
            else // new clicks
            {
                if (Event.Type == Event.MouseButtonPressed) // detect button pressed and handle case
                {
                    if(Event.MouseButton.Button == sf::Mouse::Left)
                    {
                        mouse.at(2) = mouse.at(0) + camera.at(0);
                        mouse.at(3) = mouse.at(1) + camera.at(1);
                        mouse.at(4) = 1;
                    }
                    else // right
                    {
                        mouse.at(2) = mouse.at(0) + camera.at(0);
                        mouse.at(3) = mouse.at(1) + camera.at(1);
                        mouse.at(4) = 2;
                    }
                }
            }
        }

        if(!mode.at(3))
        {
            Window.Clear();
        }

        if (mouse.at(4)) // count if we need to
        {
            mouse.at(6)++;
        }

        if(!mode.at(2))
        {
            for(unsigned int loop0 = 0; loop0 < upf; loop0++)
            {
                for (unsigned int loop1 = 0; loop1 < floor((objects.size() + 1) / ppo); loop1++) // run through each body
                {
                    if (objects.at(loop1 * ppo + 5) && (!objects.at(loop1 * ppo + 10))) // is it calculated
                    {
                        for (unsigned int loop2 = 0; loop2 < floor((objects.size() + 1)/ppo); loop2++) // compare to every other body
                        {
                            if ((loop1 != loop2) && objects.at(loop2 * ppo + 5)) // is the object relevant
                            {
                                distance.at(loop2 * ppo + 1) = objects.at(loop2 * ppo) - objects.at(loop1 * ppo);  // distance calculations
                                distance.at(loop2 * ppo + 2) = objects.at(loop2 * ppo + 1) - objects.at(loop1 * ppo + 1);
                                distance.at(loop2 * ppo) = pow(distance.at(loop2 * ppo + 1), 2) + pow(distance.at(loop2 * ppo + 2), 2);

                                if (distance.at(loop2 * ppo) < pow(objects.at(loop1 * ppo + 9) + objects.at(loop2 * ppo + 9), 2)) // collision detection
                                {
                                    objects.resize(objects.size() + ppo);
                                    distance.resize(objects.size() * 3);

                                    objects.at(objects.size() - ppo + 10) = objects.at(loop1 * ppo + 10) || objects.at(loop2 * ppo + 10); // stationary

                                    switch (int(objects.at(loop1 * ppo + 10) + objects.at(loop2 * ppo + 10))) // fixed position selection for merging objects
                                    {
                                        default: // neither object is stationary
                                        {
                                            if(objects.at(loop2 * ppo + 9) > objects.at(loop1 * ppo + 9)) // fudged method for selecting objects
                                            {
                                                objects.at(objects.size() - ppo) = objects.at(loop2 * ppo); // x
                                                objects.at(objects.size() - ppo + 1) = objects.at(loop2 * ppo + 1); // y
                                            }
                                            else
                                            {
                                                objects.at(objects.size() - ppo) = objects.at(loop1 * ppo); // x
                                                objects.at(objects.size() - ppo + 1) = objects.at(loop1 * ppo + 1); // y
                                            }
                                            break;
                                        }
                                        case 1:
                                        {
                                            if(objects.at(loop1 * ppo + 10)) // object1 is stationary
                                            {
                                                objects.at(objects.size() - ppo) = objects.at(loop1 * ppo); // x
                                                objects.at(objects.size() - ppo + 1) = objects.at(loop1 * ppo + 1); // y
                                            }
                                            else // object2 is stationary
                                            {
                                                objects.at(objects.size() - ppo) = objects.at(loop2 * ppo); // x
                                                objects.at(objects.size() - ppo + 1) = objects.at(loop2 * ppo + 1); // y
                                            }
                                            break;
                                        }
                                    }

                                    objects.at(objects.size() - ppo + 4) = objects.at(loop1 * ppo + 4) + objects.at(loop2 * ppo + 4); //mass

                                    temp.at(0) = (objects.at(loop1 * ppo + 4) * objects.at(loop1 * ppo + 2));
                                    temp.at(1) = (objects.at(loop2 * ppo + 4) * objects.at(loop2 * ppo + 2));
                                    objects.at(objects.size() - ppo + 2) = (!objects.at(objects.size() - ppo + 10)) * (temp.at(0) + temp.at(1)) / (objects.at(loop1 * ppo + 4) + (objects.at(loop2 * ppo + 4))); // x velocity

                                    temp.at(0) = (objects.at(loop1 * ppo + 4) * objects.at(loop1 * ppo + 3));
                                    temp.at(1) = (objects.at(loop2 * ppo + 4) * objects.at(loop2 * ppo + 3));
                                    objects.at(objects.size() - ppo + 3) = (!objects.at(objects.size() - ppo + 10)) * (temp.at(0) + temp.at(1)) / (objects.at(loop1 * ppo + 4) + (objects.at(loop2 * ppo + 4))); // y velocity

                                    objects.at(objects.size() - ppo + 5) = 1;

                                    objects.at(objects.size() - ppo + 6) = (objects.at(loop1 * ppo + 4) / (objects.at(loop1 * ppo + 4) + objects.at(loop2 * ppo + 4)) * objects.at(loop1 * ppo + 6))+ (objects.at(loop2 * ppo + 4) / (objects.at(loop1 * ppo + 4) + objects.at(loop2 * ppo + 4)) * objects.at(loop2 * ppo + 6)); //color
                                    objects.at(objects.size() - ppo + 7) = (objects.at(loop1 * ppo + 4) / (objects.at(loop1 * ppo + 4) + objects.at(loop2 * ppo + 4)) * objects.at(loop1 * ppo + 7))+ (objects.at(loop2 * ppo + 4) / (objects.at(loop1 * ppo + 4) + objects.at(loop2 * ppo + 4)) * objects.at(loop2 * ppo + 7));
                                    objects.at(objects.size() - ppo + 8) = (objects.at(loop1 * ppo + 4) / (objects.at(loop1 * ppo + 4) + objects.at(loop2 * ppo + 4)) * objects.at(loop1 * ppo + 8))+ (objects.at(loop2 * ppo + 4) / (objects.at(loop1 * ppo + 4) + objects.at(loop2 * ppo + 4)) * objects.at(loop2 * ppo + 8));
                                    objects.at(objects.size() - ppo + 9) = sqrt(objects.at(objects.size() - ppo + 4) / pi);

                                    objects.at(loop1 * ppo + 5) = 0; // kill parents
                                    objects.at(loop2 * ppo + 5) = 0;
                                    break;
                                }
                                else
                                {
                                    objects.at(loop1 * ppo + 2) += distance.at(loop2 * ppo + 1) / distance.at(loop2 * ppo) * objects.at(loop2 * ppo + 4) / objects.at(loop1 * ppo + 4) * gravity / upf; // gravity force calculations
                                    objects.at(loop1 * ppo + 3) += distance.at(loop2 * ppo + 2) / distance.at(loop2 * ppo) * objects.at(loop2 * ppo + 4) / objects.at(loop1 * ppo + 4) * gravity / upf;
                                }
                            }
                        }
                    }
                }
            }

            for(unsigned int loop1 = 0; loop1 < floor((objects.size() + 1) / ppo); loop1++)
            {
                if(objects.at(loop1 * ppo + 5))
                {
                    // update positions
                    objects.at(loop1 * ppo) += objects.at(loop1 * ppo + 2);
                    objects.at(loop1 * ppo + 1) += objects.at(loop1 * ppo + 3);
                }
            }
        }

        for (unsigned int loop1 = 0; loop1 < floor((objects.size() + 1) / ppo); loop1++) // draw
        {
            if(objects.at(loop1 * ppo + 5))
            {
                switch (mode.at(0))
                {
                    default:
                    {
                        sf::Shape Circle = sf::Shape::Circle(camera.at(2) * (objects.at(loop1 * ppo) - camera.at(0)), camera.at(2) * (objects.at(loop1 * ppo + 1) - camera.at(1)), (1.0 / camera.at(2)) * objects.at(loop1 * ppo + 9), sf::Color(objects.at(loop1 * ppo + 6), objects.at(loop1 * ppo + 7), objects.at(loop1 * ppo + 8)));
                        Window.Draw(Circle);
                        break;
                    }
                    case 1:
                    {
                        sf::Shape Circle = sf::Shape::Circle(objects.at(loop1 * ppo) - camera.at(0), objects.at(loop1 * ppo + 1) - camera.at(1), 1, sf::Color(objects.at(loop1 * ppo + 6), objects.at(loop1 * ppo + 7), objects.at(loop1 * ppo + 8)));
                        Window.Draw(Circle);
                        break;
                    }
                    case 2:
                    {
                        sf::Shape Line = sf::Shape::Line(objects.at(loop1 * ppo) - camera.at(0), objects.at(loop1 * ppo + 1) - camera.at(1), objects.at(loop1 * ppo) - camera.at(0) + 10 * objects.at(loop1 * ppo + 2), objects.at(loop1 * ppo + 1) - camera.at(1) + 10 * objects.at(loop1 * ppo + 3), 1.0, sf::Color(objects.at(loop1 * ppo + 6), objects.at(loop1 * ppo + 7), objects.at(loop1 * ppo + 8)));
                        Window.Draw(Line);
                        break;
                    }
                }
            }
        }

        switch (int(mouse.at(4)))
        {
            default:
            {
                break;
            }
            case 1: //left
            {
                sf::Shape Line = sf::Shape::Line(mouse.at(0), mouse.at(1) , mouse.at(2) - camera.at(0), mouse.at(3) - camera.at(1), 1, sf::Color(255,255,255));
                Window.Draw(Line);

                sf::Shape Circle = sf::Shape::Circle(mouse.at(2) - camera.at(0), mouse.at(3) -camera.at(1), mouse.at(6) / 10, sf::Color(255,255,255));
                Window.Draw(Circle);
                break;
            }
            case 2: //right
            {
                sf::Shape Circle = sf::Shape::Circle(mouse.at(2) - camera.at(0), mouse.at(3) - camera.at(1), mouse.at(6) / 10, sf::Color(255,255,255));
                Window.Draw(Circle);
                break;
            }
        }
        Window.Display();
    }

    return 0;
}
